import "dayjs/locale/zh-cn";

import * as dayjs from "dayjs";
import { type UnitType } from "dayjs";
import * as relativeTime from "dayjs/plugin/relativeTime";

dayjs.locale("zh-cn");
dayjs.extend(relativeTime);

export class DateTimeUtils {
    /**
     * 获取当前日期和时间的格式化字符串或者部分信息
     * @param format 格式化字符串
     * @param unit 时间单位（年、月、日、小时、分钟、秒）
     * @returns 当前日期和时间格式化后的字符串或者部分时间信息
     * @example
     * getCurrentDateTime(undefined, "month") // 8
     */
    static getCurrentDateTime(
        format: string = "YYYY-MM-DD HH:mm:ss",
        unit: "year" | "month" | "day" | "hour" | "minute" | "second" | "all" = "all"
    ): string | number {
        // const year = dayjs().year();
        // const month = dayjs().month() + 1; // 注意月份是从0开始的
        // const day = dayjs().date();
        // const hour = dayjs().hour();
        // const minute = dayjs().minute();
        if (unit === "month") return dayjs().month() + 1;
        return unit === "all" ? dayjs().format(format) : dayjs()[unit]();
    }

    /**
     * 格式化给定的日期时间字符串
     * @param dateTime 日期时间字符串
     * @param format 格式化字符串
     * @returns 格式化的日期时间字符串
     */
    static formatDateTime(dateTime: string, format: string = "YYYY-MM-DD HH:mm:ss"): string {
        return dayjs(dateTime).format(format);
    }

    /**
     * 解析日期时间字符串为 Day.js 实例
     * @param dateTime 日期时间字符串
     * @returns Day.js 实例
     */
    static parseDateTime(dateTime: string): dayjs.Dayjs {
        return dayjs(dateTime);
    }

    /**
     * 获取两个日期之间的差值 endDate - startDate
     * @param startDate 开始日期
     * @param endDate 结束日期
     * @param unit 时间单位（年、月、日、小时、分钟、秒）
     * @returns 差值。差值结果带有正负
     * @example
     * getDifference("2023-01-02 00:00:00", "2023-01-01 00:00:00", "day") // -1
     */
    static getDifference(
        startDate: string,
        endDate: string,
        unit: "year" | "month" | "day" | "hour" | "minute" | "second" = "day"
    ): number {
        return dayjs(endDate).diff(dayjs(startDate), unit);
    }

    /**
     * 获取日期的相对当前时间的描述
     * @param dateTime 日期时间字符串
     * @returns 相对时间描述
     * @example
     * getRelativeTime("2023-01-01 00:00:00") // "1 年前"
     */
    static getRelativeTime(dateTime: string): string {
        return dayjs(dateTime).fromNow();
    }

    /**
     * 比较日期：判断给定日期是否在今天之前
     * @param dateTime 日期时间字符串
     * @returns boolean
     * @example
     * isBeforeToday("2023-01-01 00:00:00") // true
     */
    static isBeforeToday(dateTime: string): boolean {
        return dayjs(dateTime).isBefore(dayjs());
    }

    /**
     * 根据当前日期获取之前或之后的日期
     * @param offset 偏移量
     * @param unit 时间单位（默认为 'day'）
     * @returns string 年月日
     * @example
     * // 当前日期： "2023-01-02"
     * getCurrentDateTimeOffsetDate(1, "day") // "2023-01-03"
     * getCurrentDateTimeOffsetDate(-1, "day") // "2023-01-01"
     */
    static getCurrentDateTimeOffsetDate(
        offset: number,
        unit: "year" | "month" | "day" | "hour" | "minute" | "second" = "day"
    ) {
        return dayjs().add(offset, unit).format("YYYY-MM-DD HH:mm:ss");
    }

    /**
     * 将东八区时间字符串解析为 js 日期对象 Date
     * @param dateTime - 需要解析的时间字符串: YYYY-MM-DD HH:mm:ss
     * @returns 解析后的日期对象 Date
     * @example
     * parserBeijingTimeStrToDate("2025-10-02 00:00:00") // Date {...} 2025-10-02T00:00:00.000Z
     *
     * @note 为什么要使用这个函数？
     *
     * 因为这种 2025-10-02 00:00:00 格式不带有时区信息，具体应用的时区以服务器时区为准。服务器时区为中国，该字符串就表示东八区时间。
     * 而在 node.js 中，默认的时区是 UTC+0，不是东八区 UTC+8。
     * 因此 new Date() 解析它时会解析成 UTC+0，时间往前倒了 8 小时，变成 2025-10-01T00:00:00.000Z。表示同一时刻 0 时区冰岛的时间。
     * 但有时我们可能需要的就是东八区的 Date 对象，不要减 8 小时。这就是这个函数的作用。
     *
     * 实现方式：提前加8小时，和减掉的抵扣。
     */
    static parserBeijingTimeStrToDate(dateTime: string) {
        if (typeof dateTime != "string") return dateTime;
        return new Date(this.getOffsetDate(dateTime, 8, "hour"));
    }

    /**
     * 根据给定日期获取之前或之后的日期
     * @param currentTime 日期时间字符串
     * @param offset 偏移量
     * @param unit 时间单位（默认为 'day'）
     * @returns string 年月日
     * @example
     * getOffsetDate("2023-01-02", 1, "day") // "2023-01-03"
     * getOffsetDate("2023-01-02", -1, "day") // "2023-01-01"
     */
    static getOffsetDate(
        currentTime: string,
        offset: number,
        unit: "year" | "month" | "day" | "hour" | "minute" | "second" = "day"
    ) {
        return dayjs(currentTime).add(offset, unit).format("YYYY-MM-DD HH:mm:ss");
    }

    /**
     * 固定部分日期和时间信息
     * @param options 要设置的时间部分选项
     * @param dateTime 日期时间字符串，默认为当前日期和时间
     * @returns string
     * @example
     * 比如要得到当日日期的 12:30 字符串。其他时间部分如年月日秒为当前时间
     * setDateTime({ hour: 12, minute: 30 }) // "2024-08-19 12:30:52"
     */
    static setDateTime(
        options: Partial<Record<"year" | "month" | "day" | "hour" | "minute" | "second", number>>,
        dateTime?: string | dayjs.Dayjs,
        format: string = "YYYY-MM-DD HH:mm:ss"
    ): string {
        let date = dateTime ? dayjs(dateTime) : dayjs();
        for (const key in options) {
            if (options.hasOwnProperty(key)) {
                date = date.set(key as UnitType, options[key]);
            }
        }
        return date.format(format);
    }
}
